package model

import (
    "github.com/go-gl/mathgl/mgl32"
    "git.oschina.net/zdglf/gommd/gles2"
    "log"
    "git.oschina.net/zdglf/gommd/util"
    "unsafe"
)

type Obj3DModel struct{
    program int32
    programMap map[string]int32

    obj3d *util.Obj3D


    rotx float32
    roty float32

    fovy float32



    vbuffers []map[string]GLBuf

    distance float32
    center mgl32.Vec3
    cameraPosition mgl32.Vec3
    upPos mgl32.Vec3

    x int32
    y int32
    width int32
    height int32


    viewMatrix mgl32.Mat4
    pMatrix mgl32.Mat4
    modelMatrix mgl32.Mat4
    mvMatrix mgl32.Mat4
    nMatrix mgl32.Mat4
}

func (m *Obj3DModel)LoadFile(filePath string) bool  {
    m.obj3d = new(util.Obj3D)
    log.Println("start obj load")
    if err := m.obj3d.LoadFile(filePath); err != nil{
        m.obj3d = nil
        log.Println("load obj fail")
        log.Println(err)
        return false
    }else {
        log.Println("load obj success")
        return true;
    }
}

func (m *Obj3DModel)InitParam(x int32, y int32, width int32, height int32){
    m.cameraPosition = mgl32.Vec3{2.0, 2.0, -2.0}

    m.rotx = 0
    m.roty = 0
    m.fovy = 20

    m.distance = 2.0
    m.center =  mgl32.Vec3{0.0, 0.0, 0.0}
    m.upPos = mgl32.Vec3{0,1,0.0}
    m.x = x
    m.y = y
    m.width = width
    m.height = height
    m.initVertices()
    log.Println("InitParam:", gles2.GetError())
}


func (m *Obj3DModel)initVertices(){
    m.vbuffers = make([]map[string]GLBuf, 0)
    if m.obj3d != nil{
        m.vbuffers = make([]map[string]GLBuf, m.obj3d.CountObj)

        for i:= 0;i <m.obj3d.CountObj;i++{
            m.vbuffers[i] = make(map[string]GLBuf)
            positions := m.obj3d.V[i]
            normals  := m.obj3d.Vn[i]



            tmpArr := make([]AttrArrBuf, 0)
            tmpArr = append(tmpArr, AttrArrBuf{3, positions, "aPosition"})
            tmpArr = append(tmpArr, AttrArrBuf{3, normals, "aVertexNormal"})

            for _, tmp := range tmpArr{
                buffer := make([]int32, 1)
                gles2.GenBuffers(1, buffer)
                gles2.BindBuffer(gles2.ARRAY_BUFFER, buffer[0])
                gles2.BufferData(gles2.ARRAY_BUFFER, 4 * len(tmp.array), unsafe.Pointer(&tmp.array[0]), gles2.STATIC_DRAW)

                m.vbuffers[i][tmp.attribute] = GLBuf{tmp.size, buffer[0]}

            }
            gles2.BindBuffer(gles2.ARRAY_BUFFER, 0)

            log.Println("initVertices:", gles2.GetError())
        }

    }
}

func (m *Obj3DModel)InitShader(vShader string, fShader string) bool {
    vertexShader := gles2.CreateShader(gles2.VERTEX_SHADER)
    gles2.ShaderSource(vertexShader,1, []string{vShader}, []int32{int32(len(vShader))})
    gles2.CompileShader(vertexShader)
    var compileStatus int32
    gles2.GetShaderiv(vertexShader, gles2.COMPILE_STATUS, &compileStatus)
    if compileStatus == 0{
        var maxLength int32 = 1024
        infoBytes := make([]byte, maxLength)
        var realLength int32
        gles2.GetShaderInfoLog(vertexShader, maxLength, &realLength, infoBytes)
        log.Println("vshader", string(infoBytes[:realLength]))
        gles2.DeleteShader(vertexShader)
        vertexShader = 0
    }
    if vertexShader == 0{
        return false
    }

    fragmentShader := gles2.CreateShader(gles2.FRAGMENT_SHADER)
    gles2.ShaderSource(fragmentShader, 1, []string{fShader}, []int32{int32(len(fShader))})
    gles2.CompileShader(fragmentShader)
    gles2.GetShaderiv(fragmentShader, gles2.COMPILE_STATUS, &compileStatus)
    if compileStatus == 0{
        var maxLength int32 = 1024
        infoBytes := make([]byte, maxLength)
        var realLength int32
        gles2.GetShaderInfoLog(fragmentShader, maxLength, &realLength, infoBytes)
        log.Println("fshader", string(infoBytes[:realLength]))
        gles2.DeleteShader(fragmentShader)
        fragmentShader = 0
    }
    if fragmentShader == 0{
        return false
    }
    m.program = gles2.CreateProgram()
    gles2.AttachShader(m.program, vertexShader)
    gles2.AttachShader(m.program, fragmentShader)
    gles2.LinkProgram(m.program)
    var linkStatus int32
    gles2.GetProgramiv(m.program, gles2.LINK_STATUS, &linkStatus)
    if linkStatus == 0{
        var maxLength int32 = 1024
        infoBytes := make([]byte, maxLength)
        var realLength int32
        gles2.GetProgramInfoLog(m.program, maxLength, &realLength, infoBytes)
        log.Println("link", string(infoBytes[:realLength]))
        m.program = 0
    }
    if m.program == 0{
        return false
    }
    gles2.UseProgram(m.program)
    m.programMap = make(map[string]int32)

    var count int32
    gles2.GetProgramiv(m.program, gles2.ACTIVE_ATTRIBUTES, &count)

    for i:= 0;i <int(count);i++{
        var bufSize int32 = 60
        var realSize int32
        var attr_type int32
        var nameSize int32
        buff := make([]byte, bufSize)
        gles2.GetActiveAttrib(m.program, int32(i), bufSize, &nameSize, &realSize, &attr_type, buff)
        name := string(buff[:nameSize])
        m.programMap[name] = int32(i)
        gles2.EnableVertexAttribArray(int32(i))
        log.Println(i,name)
    }
    gles2.GetProgramiv(m.program, gles2.ACTIVE_UNIFORMS, &count)
    for i:= 0;i <int(count);i++{
        var bufSize int32 = 60
        var realSize int32
        var attr_type int32
        var nameSize int32
        buff := make([]byte, bufSize)
        gles2.GetActiveUniform(m.program, int32(i), bufSize, &nameSize, &realSize, &attr_type, buff)
        name := string(buff[:nameSize])
        m.programMap[name] = int32(i)
        log.Println(i, name)
    }
    log.Println("InitShader:", gles2.GetError())

    return true
}


func (m *Obj3DModel)computeMatrices(){

    rotx_mat3 := mgl32.Rotate3DX(m.rotx)
    m.cameraPosition = rotx_mat3.Mul3x1(m.cameraPosition)
    roty_mat3 := mgl32.Rotate3DX(m.roty)
    m.cameraPosition = roty_mat3.Mul3x1(m.cameraPosition)

    m.cameraPosition = m.cameraPosition.Add(m.center)

    gles2.Enable(gles2.CULL_FACE)
    gles2.Enable(gles2.DEPTH_TEST)
    gles2.Viewport(m.x,m.y,m.width,m.height)

    ratio := float32(m.width) / float32(m.height)
    near := float32(0.1)
    far := float32(100)

    rotx_mat3 = mgl32.Rotate3DX(m.rotx)
    m.upPos = rotx_mat3.Mul3x1(m.upPos)
    roty_mat3 = mgl32.Rotate3DX(m.roty)
    m.upPos = roty_mat3.Mul3x1(m.upPos)

    m.viewMatrix = mgl32.LookAtV(m.cameraPosition, m.center, m.upPos)
    m.pMatrix = mgl32.Perspective(m.fovy, ratio, near, far)

}


func (m *Obj3DModel)setUniforms()  {
    m.modelMatrix = mgl32.Ident4()
    m.mvMatrix = m.viewMatrix.Mul4(m.modelMatrix)
    m.nMatrix = m.mvMatrix.Inv()
    m.nMatrix = m.nMatrix.Transpose()

    lightPos := mgl32.Vec3{10.0, 10.0, 10.0}
    lightColor := mgl32.Vec3{1.0, 1.0, 1.0}

    gles2.Uniform3fv(m.programMap["uLightPos"], 1, &lightPos[0])
    gles2.Uniform3fv(m.programMap["uLightColor"], 1, &lightColor[0])

    gles2.UniformMatrix4fv(m.programMap["uMVMatrix"], 1, byte(0), &m.mvMatrix[0])
    gles2.UniformMatrix4fv(m.programMap["uPMatrix"], 1, byte(0), &m.pMatrix[0])



    log.Println("setUniforms:", gles2.GetError())


}

func (m *Obj3DModel)Render(){
    if m.obj3d!=nil {
        for i := 0; i < m.obj3d.CountObj; i++ {
            m.computeMatrices()
            gles2.ClearColor(0.5, 0.5, 0.5, 1)
            gles2.ClearDepthf(1)
            gles2.Enable(gles2.DEPTH_TEST)

            gles2.BindFramebuffer(gles2.FRAMEBUFFER, 0)
            gles2.Viewport(m.x, m.y, m.width, m.height)
            gles2.Clear(gles2.COLOR_BUFFER_BIT | gles2.DEPTH_BUFFER_BIT)
            buffers := m.vbuffers[i]
            for attr, vb := range buffers {
                gles2.BindBuffer(gles2.ARRAY_BUFFER, vb.buffer)
                gles2.VertexAttribPointer(m.programMap[attr], int32(vb.size), gles2.FLOAT, byte(0), 0, nil)
            }
            //m.setSelfShadowTexture()
            m.setUniforms()
            gles2.CullFace(gles2.FRONT)
            gles2.DrawElements(gles2.TRIANGLES, int32(len(m.obj3d.FVIndex[i])), gles2.UNSIGNED_INT, unsafe.Pointer(&m.obj3d.FVIndex[i][0]))

            gles2.Flush()
        }
    }
}
